package editDistance;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 验证相似性的思路是先将图像进行缩放至指定尺寸，<br/>
 * 然后进行灰度处理，去掉颜色特征，<br/>
 * 最后对处理后的图像计算哈希值，<br/>
 * 通过比对不同图像的哈希值的汉明距离来判断图像是否相似
 *
 * @author 张峰浦
 * @create 创建时间：2020-01-17 16:52
 */
public class ImageEditDistance {

    private static int size = 32;
    private static int smallerSize = 8;

    private static double[] c;

    static {
        c = new double[size];

        for (int i = 1; i < size; i++) {
            c[i] = 1;
        }
        c[0] = 1 / Math.sqrt(2.0);
    }

    /**
     * 通过汉明距离计算相似度
     *
     * @param hash1
     * @param hash2
     * @return
     */
    public static double calSimilarity(String hash1, String hash2) {
        return calSimilarity(getHammingDistance(hash1, hash2));
    }

    /**
     * 通过汉明距离计算相似度
     *
     * @param hammingDistance
     * @return
     */
    public static double calSimilarity(int hammingDistance) {
        int length = size * size;
        double similarity = (length - hammingDistance) / (double) length;

        // 使用指数曲线调整相似度结果
        similarity = Math.pow(similarity, 2);
        return similarity;
    }

    /**
     * 通过汉明距离计算相似度<p>
     * 验证相似性的思路是先将图像进行缩放至指定尺寸，<br/>
     * 然后进行灰度处理，去掉颜色特征，<br/>
     * 最后对处理后的图像计算哈希值，<br/>
     * 通过比对不同图像的哈希值的汉明距离来判断图像是否相似
     *
     * @param image1
     * @param image2
     * @return
     * @throws IOException
     */
    public static double calSimilarity(File image1, File image2) throws IOException {
        return calSimilarity(getHammingDistance(image1, image2));
    }

    /**
     * 获得汉明距离
     *
     * @param hash1
     * @param hash2
     * @return
     */
    public static int getHammingDistance(String hash1, String hash2) {
        int counter = 0;
        for (int k = 0; k < hash1.length(); k++) {
            if (hash1.charAt(k) != hash2.charAt(k)) {
                counter++;
            }
        }
        return counter;
    }

    /**
     * 获得汉明距离
     *
     * @param image1
     * @param image2
     * @return
     * @throws IOException
     */
    public static int getHammingDistance(File image1, File image2) throws IOException {
        return getHammingDistance(getHash(image1), getHash(image2));
    }

    /**
     * 返回二进制字符串，类似“001010111011100010”，可用于计算汉明距离
     *
     * @param imageFile
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static String getHash(File imageFile) throws IOException {
        BufferedImage img = ImageIO.read(imageFile);

        /*
         * 缩小尺寸。像普通散列一样，pHash从一个小图像开始。
         * 但是，图像大于8x8；32x32是一个很好的大小。这是
         * 是为了简化DCT的计算，而不是因为它是
         * 需要降低高频。
         */
        img = resize(img, size, size);

        /*
         * 减少颜色。图像被缩小为灰度，以便进一步
         * 简化计算次数。
         */
        img = grayscale(img);

        double[][] vals = new double[size][size];

        for (int x = 0; x < img.getWidth(); x++) {
            for (int y = 0; y < img.getHeight(); y++) {
                vals[x][y] = getBlue(img, x, y);
            }
        }

        /*
         * 计算离散余弦变换。DCT将图像分割成
         * 频率和标量。虽然JPEG使用8x8 DCT，但此算法
         * 使用32x32 DCT。
         */
        // long start = System.currentTimeMillis();
        double[][] dctVals = applyDCT(vals);
        // System.out.println("耗时: " + (System.currentTimeMillis() - start));

        /*
         * 减少离散余弦变换。这是神奇的一步。虽然DCT是32x32，但是
         * 保持左上角8x8。它们代表图片。
         *
         * 计算平均值。像平均散列一样，计算平均值DCT值（仅使用8x8 DCT低频值，不包括
         * 第一项，因为直流系数可以显著不同从其他值中减去平均值）。
         *
         */
        double total = 0;

        for (int x = 0; x < smallerSize; x++) {
            for (int y = 0; y < smallerSize; y++) {
                total += dctVals[x][y];
            }
        }
        total -= dctVals[0][0];

        double avg = total / (double) ((smallerSize * smallerSize) - 1);

        /*
         * 进一步降低离散余弦变换。这是神奇的一步。设置64个哈希位
         * 到0或1，具体取决于64个DCT值中的每一个值是否高于或
         * 低于平均值。结果没有告诉我们实际的低点频率；它只是告诉我们
         * 平均频率。只要图像的总体结构保持不变；这可以在伽马射线下存活下来
         * 和颜色直方图调整没有问题。
         */
        StringBuilder hash = new StringBuilder();

        for (int x = 0; x < smallerSize; x++) {
            for (int y = 0; y < smallerSize; y++) {
                if (x != 0 && y != 0) {
                    hash.append((dctVals[x][y] > avg ? "1" : "0"));
                }
            }
        }

        return hash.toString();
    }

    private static BufferedImage resize(BufferedImage image, int width, int height) {
        BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = resizedImage.createGraphics();
        g.drawImage(image, 0, 0, width, height, null);
        g.dispose();
        return resizedImage;
    }

    private static BufferedImage grayscale(BufferedImage img) {
        new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(img, img);
        return img;
    }

    private static int getBlue(BufferedImage img, int x, int y) {
        return (img.getRGB(x, y)) & 0xff;
    }

    private static double[][] applyDCT(double[][] f) {
        int N = size;

        double[][] F = new double[N][N];
        for (int u = 0; u < N; u++) {
            for (int v = 0; v < N; v++) {
                double sum = 0.0;
                for (int i = 0; i < N; i++) {
                    for (int j = 0; j < N; j++) {
                        sum += Math.cos(((2 * i + 1) / (2.0 * N)) * u * Math.PI)
                                * Math.cos(((2 * j + 1) / (2.0 * N)) * v * Math.PI) * (f[i][j]);
                    }
                }
                sum *= ((c[u] * c[v]) / 4.0);
                F[u][v] = sum;
            }
        }
        return F;
    }


    // 改变成二进制码
    public static String[][] getPX(String args) {
        int[] rgb = new int[3];
        File file = new File(args);
        BufferedImage bi = null;
        try {
            bi = ImageIO.read(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
        int width = bi.getWidth();
        int height = bi.getHeight();
        int minx = bi.getMinX();
        int miny = bi.getMinY();
        String[][] list = new String[width][height];
        for (int i = minx; i < width; i++) {
            for (int j = miny; j < height; j++) {
                int pixel = bi.getRGB(i, j);
                rgb[0] = (pixel & 0xff0000) >> 16;
                rgb[1] = (pixel & 0xff00) >> 8;
                rgb[2] = (pixel & 0xff);
                list[i][j] = rgb[0] + "," + rgb[1] + "," + rgb[2];
            }
        }
        return list;
    }

    public static Map<String , Integer> compareImage(String imgPath1, String imgPath2) {
        String[] images = {imgPath1, imgPath2};
        if (images.length == 0) {
            System.out.println("Usage >java BMPLoader ImageFile.bmp");
            System.exit(0);
        }
        // 分析图片相似度 begin
        String[][] list1 = getPX(images[0]);
        String[][] list2 = getPX(images[1]);
        int xiangsi = 0;
        int busi = 0;
        int i = 0, j = 0;
        for (String[] strings : list1) {
            if ((i + 1) == list1.length) {
                continue;
            }
            for (int m = 0; m < strings.length; m++) {
                try {
                    String[] value1 = list1[i][j].toString().split(",");
                    String[] value2 = list2[i][j].toString().split(",");
                    int k = 0;
                    for (int n = 0; n < value2.length; n++) {
                        if (Math.abs(Integer.parseInt(value1[k]) - Integer.parseInt(value2[k])) < 5) {
                            xiangsi++;
                        } else {
                            busi++;
                        }
                    }
                } catch (RuntimeException e) {
                    continue;
                }
                j++;
            }
            i++;
        }
        list1 = getPX(images[1]);
        list2 = getPX(images[0]);
        i = 0;
        j = 0;
        for (String[] strings : list1) {
            if ((i + 1) == list1.length) {
                continue;
            }
            for (int m = 0; m < strings.length; m++) {
                try {
                    String[] value1 = list1[i][j].toString().split(",");
                    String[] value2 = list2[i][j].toString().split(",");
                    int k = 0;
                    for (int n = 0; n < value2.length; n++) {
                        if (Math.abs(Integer.parseInt(value1[k]) - Integer.parseInt(value2[k])) < 5) {
                            xiangsi++;
                        } else {
                            busi++;
                        }
                    }
                } catch (RuntimeException e) {
                    continue;
                }
                j++;
            }
            i++;
        }
        String baifen = "";
        try {
            baifen = ((Double.parseDouble(xiangsi + "") / Double.parseDouble((busi + xiangsi) + "")) + "");
            baifen = baifen.substring(baifen.indexOf(".") + 1, baifen.indexOf(".") + 3);
        } catch (Exception e) {
            baifen = "0";
        }
        if (baifen.length() <= 0) {
            baifen = "0";
        }
        if (busi == 0) {
            baifen = "100";
        }
        System.out.println("相似像素数量：" + xiangsi + " 不相似像素数量：" + busi + " 相似率：" + Integer.parseInt(baifen) + "%");
        Map<String , Integer> map = new HashMap<String , Integer>();
        map.put("xiangsi",xiangsi);
        map.put("busi",busi);
        map.put("baifenbi",Integer.parseInt(baifen));

        return map;
    }
}
